#ifndef MEMORY_H_
#define MEMORY_H_

#include <iostream>
#include <string.h>
#include <stdint.h>
#include <algorithm>
#include <map>
#include <fstream>
#include <vector>
#include <bitset>

#include "constants.h"


using namespace std;

typedef std::pair<uint32_t, uint32_t> pair_page;
typedef std::pair<int32_t, uint32_t>  pair_value;

class AccessLocation {
    private:
        uint32_t _LM;
        uint32_t _RLM;
        uint32_t _MM;
        uint32_t _accesses;
    public:
        AccessLocation(){_LM=_RLM=_MM=0;}
        ~AccessLocation(){}
        inline uint32_t getLM()  { return _LM;}
        inline uint32_t getRLM() { return _RLM;}
        inline uint32_t getMM()  { return _MM;}
        inline uint32_t getTotalAccesses() { return _LM+_RLM+_MM;}
        inline uint32_t getAccesses() { return _accesses;}
        inline void  Access(uint32_t location,uint32_t thread) {
            _accesses++;
            if (location==MAINMEMORY){  _MM++; return;}
            if (location==thread)    {  _LM++; return;}
            _RLM++;
            return;
        }
        inline void resetAccesses() { _accesses=0;}
        inline void reset() {_LM=_RLM=_MM=_accesses=0;}
};

class PageInfo {
    public:
        uint32_t _location;
        uint32_t _accesses;
        bitset<MAXTHREADS> _L1Location;
        bitset<MAXTHREADS> _L2Location;

        AccessLocation _tidaccesses[MAXTHREADS];
    public:
        PageInfo(){
            _location=_accesses=0;
            _L1Location.reset();
            _L2Location.reset();
            for (uint32_t i=0;i<MAXTHREADS;i++) {
                _tidaccesses[i].reset();
            }
        }
        PageInfo(uint32_t location,uint32_t accesses){
            for (uint32_t i=0;i<MAXTHREADS;i++) {
                _tidaccesses[i].reset();
            }
            _location=location;
            _accesses=accesses;
        }
        void reset() {
            _location=_accesses=0;
            _L1Location.reset();
            _L2Location.reset();
            for (uint32_t i=0;i<MAXTHREADS;i++) {
                _tidaccesses[i].reset();
            }
        }
        ~PageInfo(){}
};

class Memory {
    private:
        //page,LM location(processor) or MAIN Memory
        std::map<uint64_t,PageInfo> _pagetable;
        // Current LM status
        std::map<uint64_t,uint32_t> * _LM;
        // Status previous to migration
        std::map<uint64_t,uint32_t> * _preLM;
        std::vector<pair_page> * _sortedAccesses;
        uint32_t *_LMnumpages;
        uint32_t _pagesize;
        uint32_t _LMcount;
        uint32_t _LMsize;
        uint32_t _threads;
        uint32_t _migrate;

        // Total Migrations
        uint32_t *_migrations;
        // Last Number of Migration
        uint32_t *_lastmigration;
        // Factor of success
        std::vector<pair_value> *_migrationSuccess;

    public:
        Memory(uint32_t threads,uint32_t pagesize,uint32_t LMsize);
        ~Memory();
        inline uint32_t getMigrations(uint32_t thread){return _migrations[thread];}
        inline uint32_t getLMSize() {return _LMsize;};
        inline uint32_t getNumThreads() {return _threads;};
        inline uint32_t getPagesize() {return _pagesize;};
        inline uint32_t getMigrate() {return _migrate;};
        void reset();
        void resetPageTable();
        void setLMSize(uint32_t LMSize);
        void setMigrate(uint32_t migrate);
        void dumpPageTable(std::ofstream & out);
        void dumpExtendedPageTable(std::ofstream & out,uint32_t threshold);
        uint32_t access(uint64_t page,uint32_t thread);
        uint32_t getLocation(uint64_t page);
        void setLocation(uint64_t page,uint32_t location);
        uint64_t getPageInfo(uint64_t page,uint32_t *accesses,uint32_t *location);
        uint32_t migrate(uint64_t page,uint32_t thread,uint32_t location);
        void do_ProfileDynamic_migrate(void);
        void setPage(uint64_t page,uint32_t location);
        uint32_t getNumPages(uint32_t location);
        void calculateMigrationSuccess();
        std::vector<pair_value>  getMigrationSuccess(uint32_t thread);
        void insertMostAccess(uint64_t page,uint32_t thread,uint32_t accesses);
        bool findLMVictim(uint32_t tid,uint64_t page,uint32_t accesses,uint64_t &victim);
        void deleteMostAccess();
        uint32_t getPageAccesses(uint64_t page,uint32_t thread);

        void resetPageAccesses(uint64_t page,uint32_t thread);
        void resetPageAccesses(uint64_t page);
        void resetCacheLocation(uint64_t line,int cacheLevel,uint32_t thread);
        void resetAllCacheLocation(uint64_t line,int cacheLevel);
        void setCacheLocation(uint64_t line,int cacheLevel,uint32_t thread);
        void getCacheLocation(uint64_t line,std::bitset<MAXTHREADS> & L1,
                               std::bitset<MAXTHREADS> & L2);
};

#endif /*MEMORY_H_*/
